var __Calendars = {};
var DAY_S  = 60*60*24;
var DAY_MS = DAY_S*1000;

var MONTH_NAMES, DAY_NAMES;

if (typeof(jQuery) == 'undefined') {
    alert('jQuery is required!');
} else if (typeof(jQuery.ui) == 'undefined') {
    alert('jQuery UI is required!');
} else {
    (function(){
        $.fn.extend({
            'calendar': function(options) {
                var C = new Calendar(this, options);
                C.init();
                Calendar.set(C);
            }
        });
    })(jQuery);
}

function Calendar(element, options) {
    this.currentDate = null;
    this.uuid        = null;
    this.element     = element;
    this.table       = null;
    this.controls    = null;
    
    this.items = {};
    this.itemsByDate = {};
    this.itemsByTime = {};
    this.tasks = [];
    
    this.options = $.extend(true, {
        view:       'month',
        weekStart:  0,
        years:      ['-10','+10'],
        timeStep:   30,
        
        monthNames: {
            long:  ['January', 'February', 'March', 'April', 'May', 'June', 'July', 
                    'August', 'September', 'October', 'November', 'December'],
            short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 
                    'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        },
        dayNames: {
            long:  ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
            short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
        },
        
        prevText: '&laquo;',
        nextText: '&raquo;',
        
        textMonth: 'Month',
        textWeek:  'Week',
        textDay:   'Day',
        
        itemClick: function() {
        },
        
        onShow: function() {
        }
    }, options);
    
    this.init = function() {
        this.uuid = this.generateUUID();
        this.element
            .addClass('calendar-container')
            .data('calendar', this);
            
        MONTH_NAMES = this.options.monthNames;
        DAY_NAMES   = this.options.dayNames;
        
        this.loadURLParams();
        if (this.options.date) {
            this.setDate(this.options.date);
        } else {
            this.setDate();
        }
        
        if (this.options.data) {
            for (var i=0; i<this.options.data.length; i++) {
                this.addItem(this.options.data[i]);
            }
        }
        this.tasks = this.options.tasks
        
        this.drawControls();
        this.draw(true);
    };
    
    this.generateUUID = function() {
        var uuid = "";
        var buffer = "qwertyuiopasdfghjklzxcvbnm1234567890";
        for(var i=0; i<16; i++) {
            uuid += buffer.substr(Math.round(Math.random()*(buffer.length-1)), 1)
                          .toUpperCase();
        }
        
        return uuid;
    };
    
    this.addItem = function(item) {
        if (typeof(item.id) == 'undefined') {
            item.id = this.generateUUID();
        }
        this.items[item.id] = item;
        this.refactorItems();
    };
    this.refactorItems = function() {
        var item;
        this.itemsByDate = {};
        this.itemsByTime = {};
        
        for (x in this.items) {
            item = this.items[x];
            var d = new MyDate(item.start_datetime);
            var date = d.format('Y-m-d');
            var time = d.format('Y-m-dTH-i');
            if (typeof(this.itemsByDate[date]) == 'undefined') {
                this.itemsByDate[date] = [];
            }
            if (typeof(this.itemsByTime[time]) == 'undefined') {
                this.itemsByTime[time] = [];
            }
            this.itemsByDate[date].push(item);
            this.itemsByTime[time].push(item);
        }
    };
    this.findItem = function(id) {
        for (x in this.items) {
            if (this.items[x].id == id) {
                return this.items[x];
            }
        }
        
        return false;
    };
    this.findItemElement = function(id) {
        return this.table.find('#item-'+id);
    };
    this.moveItem = function(itemElement, td) {
        var c = td.find('.calendar-items-container');
        if (! c.length) {
            c = $('<div class="calendar-items-container">').appendTo(td);
        }
        
        var item    = itemElement.data('item');
        
        var newd = new MyDate(td.attr('date').replace(/T(\d+)-(\d+)$/g, ' $1:$2'));
        var newDate = newd.format('Y-m-d');
        var newTime = newd.format('H:i');
        
        /*var original = new MyDate(item.start_datetime);
        var originalDate = original.format('Y-m-d');
        var originalTime = original.format('H:i');*/
        
        c.append(itemElement);
        
        this.items[item.id].start_datetime = newd.format('Y-m-d H:i');
        this.refactorItems();
    };
    this.removeItem = function(itemId) {
        var item = this.items[itemId];
        if (item) {
            if (confirm('Do you want to delete "'+item.title+'"?')) {
                var span = $('span#item-'+itemId);
                var d = new MyDate(item.start_datetime);
                var date = d.format('Y-m-d');
                var time = d.format('Y-m-dTH-i');
                for (var i=0; i<this.itemsByTime[time]; i++) {
                    if (this.itemsByTime[time][i].id == itemId) {
                        this.itemsByTime[time].splice(i, 1);
                        break;
                    }
                }
                for (var i=0; i<this.itemsByDate[date]; i++) {
                    if (this.itemsByDate[date][i].id == itemId) {
                        this.itemsByDate[date].splice(i, 1);
                        break;
                    }
                }
                delete(this.items[itemId]);
                this.refactorItems();
                span.remove();
            }
        }
    };
    
    this.load = function(url) {
        var params = this.parseURLParams(url);
        this.setDate(params.date);
        this.options.view = params.view;
        location.href = this.getURLParams();
        
        this.redraw();
    };
    
    this.loadURLParams = function() {
        params = this.parseURLParams();
        if (params) {
            this.options.date = params.date;
            this.options.view = params.view;
        }
    };
    
    this.parseURLParams = function(url) {
        var ret = false;
        var url  = url ? url : location.href;
        var pos  = url.indexOf("#!");
        if (pos>0) {
            var items =  url.substr(pos+2).split("/");
            ret = {
                date: items[0] ? items[0] : (new MyDate()).format('Y-m-d'),
                view: items[1] ? items[1] : 'month'
            };
        }
        return ret;
    };
    
    this.setDate = function(date) {
        if (date) {
            this.currentDate = new MyDate(date);
        } else {
            this.currentDate = new MyDate();
        }
    };
    this.getDate = function(format) {
        if (format) {
            return this.currentDate.format(format);
        } else {
            return this.currentDate;
        }
    };
    
    this.getURLParams = function(date, view) {
        ret = "#!";
        ret += (date ? date : this.getDate('Y-m-d')) + "/";
        ret += view ? view : this.options.view;
        return ret; 
    };
    
    this.getURLParamsDir = function(amount, date, view) {
        var d = this.getDate();
        if (date) {
            d = new MyDate(date);
        }
        var v = this.options.view;
        if (view) {
            v = view;
        }
        
        var params = {};
        switch(this.options.view) {
            case 'month':
                var params = this.getURLParams(d.addMonth(amount).format('Y-m-d'), v);
                break;
            
            case 'week':
                var params = this.getURLParams(d.addTime(amount*7*DAY_MS).format('Y-m-d'), v);
                break;
                
            case 'day':
                var params = this.getURLParams(d.addTime(amount*DAY_MS).format('Y-m-d'), v);
                break;
        }
        
        return params;
    }
    this.getURLParamsPrev = function(date, view) {
        return this.getURLParamsDir(-1, date, view);
    };
    this.getURLParamsNext = function(date, view) {
        return this.getURLParamsDir(1, date, view);
    };
    
    this.draw = function() {
        switch(this.options.view) {
            case 'day':
                this.drawDay();
                break;
            
            case 'week':
                this.drawWeek();
                break;
        
            default:
            case 'month':
                this.drawMonth();
                break;
        }
        this.onShow();
        this.table.find('td').droppable({
            accept: '.calendar-item',
            hoverClass: 'drop-hover',
            drop: function(e, ui) {
                var calendar = $(this).parent().parent().parent().parent().data('calendar');
                if (calendar) {
                    var td = $(this);
                    var item = ui.draggable;
                    calendar.moveItem(item, td);
                } else {
                    console.error('Invalid request. Calendar Not Found.');
                }
            }
        });
    };
    
    this.onShow = function() {
        this.options.onShow(this);
    };
    
    this.redraw = function() {
        var view = this.options.view;
        this.reset();
        this.draw();
        this.controls.find('.view .' + view).addClass('selected');
        this.updateControls(this.getDate('Y-m-d'), view);
    };
    
    this.drawDay = function() {
        this.element.addClass('view-day');
        this.controls.find('.view .day').addClass('selected');
        
        this.table = $('<table>').appendTo(this.element);
        this.drawDaysUI(1);
        
    };
    this.drawWeek = function() {
        this.element.addClass('view-week');
        this.controls.find('.view .week').addClass('selected');
        
        this.table = $('<table>').appendTo(this.element);
        this.drawDaysUI(7);
    };
    this.drawDaysUI = function(numDays) {
        var thead = $('<thead>').appendTo(this.table);
        var tr = $('<tr>').appendTo(thead);
        tr.append('<th width="9%">');
        
        var date, k, t;
        
        if (numDays>1) {
            var offsetDays = this.getDate()._date.getDay() - this.options.weekStart;
            offsetDays = offsetDays<0 ? numDays+offsetDays : offsetDays;
            var startDate = this.getDate()._date;
            startDate = new Date(startDate.getTime() - offsetDays*DAY_MS);
            var perc = 91/numDays;
            for (var i=0, k=i; i<numDays; i++) {
                k=i+this.options.weekStart;
                if (k>(numDays-1)) {
                    k-=numDays;
                }
                
                t = startDate.getTime() + i * DAY_MS;
                date = new Date(t);
                tr.append('<th width="'+perc+'%">'+this.options.dayNames.long[k]+', '+date.getDate()+'</th>')
            }
        }else{
            startDate = this.getDate()._date;
            k = startDate.getDay();
            tr.append('<th width="91%">'+this.options.dayNames.long[k]+', '+startDate.getDate()+'</th>')
        }
        
        // draw Rows
        var tbody = $('<tbody>').appendTo(this.table);
        var timeStart, timeEnd, hourStart, hourEnd, minStart, minEnd;
        var timeStep = this.options.timeStep;
        for (i=0; i<(60/timeStep*24); i++) {
            tr = $('<tr>').appendTo(tbody);
            timeStart = this.getDate().min2time(i*timeStep);
            
            timeEnd   = this.getDate().min2time((i+1)*timeStep);
            
            var th = $('<th class="label">'+timeStart+" - "+timeEnd+'</th>');
            tr.append(th);
            if(timeStart.match(/^\d+:00/)) {
                th.addClass('hour-start');
            }
            var td, d, tmp;
            var start = new MyDate(startDate.getFullYear()+'-'+(startDate.getMonth()+1)+'-'+startDate.getDate())
            
            tmp = timeStart.match(/(\d+)/g);
            start.setHours(parseInt(tmp[0], 10));
            start.setMinutes(parseInt(tmp[1], 10));
            
            for (var j=0; j<numDays; j++) {
                d = start.addTime(j*DAY_MS);
                td = $('<td>&nbsp;</td>');
                td.attr('date', d.format('Y-m-dTH-i'));
                
                if (numDays>1 && 
                    d.format('Y-m-d') == this.getDate('Y-m-d')) {
                    td.addClass('current')
                }
                tr.append(td);
                
                this.drawItemsFor(d.format('Y-m-dTH-i'));
            }
        }
    };
    this.drawMonth = function() {
        this.element.addClass('view-month');
        this.controls.find('.view .month').addClass('selected');
        
        this.table = $('<table>').appendTo(this.element);
        var thead = $('<thead>').appendTo(this.table);
        
        // draw Header
        var tr = $('<tr>').appendTo(thead);
        for (var i=0,k=i; i<7; i++) {
            k=i+this.options.weekStart;
            if(k>6) {
                k-=7;
            }
            tr.append('<th width="14%">'+this.options.dayNames.long[k]+'</th>')
        }
        
        // draw Rows
        var tbody = $('<tbody>').appendTo(this.table);
        var timeStart, timeEnd;
        var timeStep = this.options.timeStep;
        
        var startDate = new Date(this.getDate('Y'),this.getDate('m')-1,1);
        var offset = startDate.getDay() - this.options.weekStart;
        offset = offset<0 ? 7+offset : offset;
        startDate = new Date(startDate.getTime() - offset*DAY_MS);
        var td, date, mdate, passed=0;
        var theMonth = parseInt(this.getDate('m'), 10);
        for (i=0,k=0; i<6; i++) {
            tr = $('<tr>').appendTo(tbody);
            for (var j=0; j<7; j++,k++) {
                passed = k * DAY_MS
                date = new Date(startDate.getTime() + passed);
                mdate = new MyDate(date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate());
                td = $('<td>');
                td.attr('date', mdate.format('Y-m-d'));
                if(theMonth != date.getMonth()+1) {
                    td.addClass('other-month');
                }
                if(this.getDate('Y-m-d') == mdate.format('Y-m-d')) {
                    td.addClass('current');
                }
                td.append('<span class="date-label">'+date.getDate()+'</span>');
                tr.append(td);
                
                this.drawItemsFor(mdate.format('Y-m-d'));
            }
        }
    };
    this.drawControls = function() {
        var sel;
        this.controls = $('<div class="controls">').appendTo(this.element);
        
        var prev = $('<a class="btn prev">');
        prev
            .data('calendar', this)
            .appendTo(this.controls)
            .html(this.options.prevText)
            .attr('href', this.getURLParamsPrev())
            .click(function(){
                var cal = $(this).data('calendar');
                cal.load(this.href);
                return false;
            });
        
        var next = $('<a class="btn next">');
        next
            .data('calendar', this)
            .appendTo(this.controls)
            .html(this.options.nextText)
            .attr('href', this.getURLParamsNext())
            .click(function(){
                var cal = $(this).data('calendar');
                cal.load(this.href);
                return false;
            });
        
        
        var ym = $('<div class="change-date">').appendTo(this.controls);
        var selMonth = $('<select class="month">').appendTo(ym);
        for(var i=0; i<this.options.monthNames.long.length; i++) {
            var m=this.options.monthNames.long;
            sel = i+1==this.getDate('m') ? ' selected="selected"' : '';
            selMonth.append('<option value="'+(i+1)+'"'+sel+'>'+m[i]+'</option>');
        }
        selMonth
            .data('calendar', this)
            .change(function(){
                var cal = $(this).data('calendar');
                var y = cal.getDate('Y');
                var d = cal.getDate('d');
                cal.setDate(y+'-'+this.value+'-'+d);
                location.href = cal.getURLParams(cal.getDate('Y-m-d'));
                cal.redraw();
            });
        
        var selYear = $('<select class="year">').appendTo(ym);
        var start = this.options.years[0];
        if (start.toString().match(/^[-+]/)) {
            start = parseInt(this.getDate('Y'), 10) + parseInt(start, 10);
        }
        var end = this.options.years[1];
        if (end.toString().match(/^[-+]/)) {
            end = parseInt(this.getDate('Y'), 10) + parseInt(end, 10);
        }
        if(end<start) {
            end=start;
        }
        for (i=start; i<=end; i++) {
            sel = i==this.getDate('Y') ? ' selected="selected"' : '';
            selYear.append('<option value="'+i+'"'+sel+'>'+i+'</option>');
        }
        selYear
            .data('calendar', this)
            .change(function(){
                var cal = $(this).data('calendar');
                var m = cal.getDate('m');
                var d = cal.getDate('d');
                cal.setDate(this.value+'-'+m+'-'+d);
                location.href = cal.getURLParams(cal.getDate('Y-m-d'));
                cal.redraw();
            });
        
        var view = $('<div class="view">').appendTo(this.controls);
        var month = $('<a class="btn month">');
        month
            .attr('href', this.getURLParams(null, 'month'))
            .appendTo(view)
            .html(this.options.textMonth)
            .data('calendar', this)
            .click(function(){
                var cal = $(this).data('calendar');
                cal.options.view = 'month';
                cal.redraw();
            });
        
        var week = $('<a class="btn week">');
        week
            .attr('href', this.getURLParams(null, 'week'))
            .appendTo(view)
            .html(this.options.textWeek)
            .data('calendar', this)
            .click(function(){
                var cal = $(this).data('calendar');
                cal.options.view = 'week';
                cal.redraw();
            });
        
        var day = $('<a class="btn day">');
        day
            .attr('href', this.getURLParams(null, 'day'))
            .appendTo(view)
            .html(this.options.textDay)
            .data('calendar', this)
            .click(function(){
                var cal = $(this).data('calendar');
                cal.options.view = 'day';
                cal.redraw();
            });
        
        view.find('.btn.' + this.options.view).addClass('selected');
    }
    
    this.drawAllItems = function() {
        this.table.find('.calendar-item').remove();
        
        switch(this.options.view) {
            case 'month':
                for (x in this.itemsByDate) {
                    this.drawItemsFor(x);
                }
                break;
                
            case 'day':
            case 'week':
                for (x in this.itemsByTime) {
                    this.drawItemsFor(x);
                }
                break;
        }
    };
    
    this.redrawItems = function(index) {
       this.table.find('.calendar-item').remove();
       this.drawItemsFor(index);
    };
    
    this.drawItemsFor = function(index) {
        var items, item, i, el, rem, td, td2, c;
        switch(this.options.view) {
            case 'month':
                items = this.itemsByDate[index];
                if (items && items.length) {
                    td  = this.table.find('td[date='+index+']');
                    c   = td.children('.calendar-items-container').eq(0);
                    if (! c.length) {
                        c = $('<div class="calendar-items-container">').appendTo(td);
                    }
                    for(i=0; i<items.length; i++) {
                        item = items[i];
                        el = this.createItemHTML(item);
                        c.append(el)
                    }
                }
                break;
            
            case 'week':
            case 'day':
                items = index ? this.itemsByTime[index] : this.items;
                if (items && items.length) {
                    td = this.table.find('td[date='+index+']');
                    c   = td.children('.calendar-items-container').eq(0);
                    if (! c.length) {
                        c = $('<div class="calendar-items-container">').appendTo(td);
                    }
                    
                    for(i=0; i<items.length; i++) {
                        item = items[i];
                        el = this.createItemHTML(item);
                        el.css({
                            top:        20*i,
                            width:      td.width() - 30
                        });
                        c.append(el)
                    }
                }
                break;
        }
    };
    
    this.createItemHTML = function(item) {
        var el = $('<span class="calendar-item">');
        el
            .attr('id', 'item-'+item.id)
            .data('itemId', item.id)
            .data('item', item)
            .data('calendar', this)
            .html(item.title ? item.title : '&nbsp;')
        
        var rem = $('<a href="javascript:void(0)" class="remove">x</a>');
        rem
            .data('calendar', this)
            .click(function(){
                var cal = $(this).data('calendar');
                cal.removeItem($(this).parent().data('itemId'));
                
                return false;
            });
        el.append(rem);
        
        el.click(function() {
            var cal = el.data('calendar');
            var item = el.data('item');
            cal.options.itemClick(item, cal);
        });
        
        el.draggable({
            revert: 'invalid',
            stack: '.calendar-container .calendar-item',
            helper: 'clone'
        });
        
        return el;
    };
    
    this.updateControls = function(date, view) {
        d = this.getDate();
        if (date) {
            d = new MyDate(date);
        }
        
        v = this.options.view;
        if (view) {
            v = view;
        }
        
        // prev,next
        this.controls.find('.btn.next').attr('href', this.getURLParamsNext(d.format('Y-m-d'),v));
        this.controls.find('.btn.prev').attr('href', this.getURLParamsPrev(d.format('Y-m-d'),v));
        
        // select month
        this.controls.find('.change-date option').removeAttr('selected');
        var index = this.controls
                        .find('.change-date .month option[value='+(d._date.getMonth()+1)+']')
                        .index();
        this.controls.find('.change-date .month')[0].selectedIndex = index;
        
        // select year
        var index = this.controls
                        .find('.change-date .year option[value='+d._date.getFullYear()+']')
                        .index();
        this.controls.find('.change-date .year')[0].selectedIndex = index;
        
        // view month
        var params = this.getURLParams(d.format('Y-m-d'), 'month');
        this.controls.find('.view .month').attr('href', params);
        
        // view week
        var params = this.getURLParams(d.format('Y-m-d'), 'week');
        this.controls.find('.view .week').attr('href', params);
        
        // view day
        var params = this.getURLParams(d.format('Y-m-d'), 'day');
        this.controls.find('.view .day').attr('href', params);
    };
    this.reset = function() {
        this.element
            .removeClass('view-month')
            .removeClass('view-week')
            .removeClass('view-day');
        this.table.remove();
        this.controls.find('.view .btn').removeClass('selected')
    };
};
Calendar.get = function(uuid) {
    return __Calendars[uuid];
}
Calendar.set = function(c) {
    __Calendars[c.uuid] = c;
}
/**
 * Custom Date Object
 *
 * @param  String  date
 *         actual date value in format:
 *         yyyy-mm-dd or yyyy-mm-dd hh:mm:ss 
 */
function MyDate(date) {
    this._date = new Date();
    if (date) {
        var d = date.match(/(\d+)/g);
        for(var i=0; i<d.length; i++){
            d[i] = parseInt(d[i], 10);
        }
        
        var h=0,m=0,s=0;
        if (d.length>3) {
            h = parseInt(d[3], 10); h = h>0 ? (h>23 ? 23 : h) : 0;
            m = parseInt(d[4], 10); m = m>0 ? (m>59 ? 59 : m) : 0;
            s = parseInt(d[5], 10); s = s>0 ? (s>59 ? 59 : s) : 0;
            this._date = new Date(d[0], parseInt(d[1], 10)-1, d[2], h, m, s);
        }else{
            this._date = new Date(d[0], parseInt(d[1], 10)-1, d[2]);
        }
    }
    
    this.format = function(format) {
        var ret = "", val="";
        var items = format.split(/(.{1})/g);
        for (var i=0; i<items.length; i++) {
            val = items[i];
            switch(items[i]) {
                case "Y":
                    val = this._date.getFullYear();
                    break;
                case "m":
                    val = this._date.getMonth() + 1;
                    val = val>9 ? val : '0'+val;
                    break;
                case "M":
                    val = this._date.getMonth();
                    val = MONTH_NAMES.long[val];
                    break;
                case "d":
                    val = this._date.getDate();
                    val = val>9 ? val : '0'+val;
                    break;
                case "D":
                    val = this._date.getDay();
                    val = DAY_NAMES.long[val];
                    break;
                case "H":
                    val = this._date.getHours();
                    val = val>9 ? val : '0'+val;
                    break;
                case "i":
                    val = this._date.getMinutes();
                    val = val>9 ? val : '0'+val;
                    break;
                case "s":
                    val = this._date.getSeconds();
                    val = val>9 ? val : '0'+val;
                    break;
            }
            
            ret += val;
        }
        
        return ret;
    };
    
    this.setHours = function(h) {
        this._date.setHours(h);
    };
    this.setMinutes = function(m) {
        this._date.setMinutes(m);
    };
    
    this.getDate = function() {
        return parseInt(this.format('d'), 10);
    };
    this.getDay = function() {
        return this.format('D');
    };
    this.getWeekDay = function() {
        return this._date.getDay();
    };
    this.min2time = function(minutes) {
        var time = minutes;
        var h = Math.floor(minutes/60);
        h = h>9 ? (h>23?'00':h) : '0'+h;
        
        var m = minutes%60;
        m = m>9 ? m : '0'+m
        
        return h+":"+m;
    };
    this.addTime = function(ms) {
        var d = new Date(this._date.getTime() + ms);
        var str = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();
        str += " " + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
        return new MyDate(str);
    };
    this.addMonth = function(add) {
        var year  = this._date.getFullYear();
        var month = this._date.getMonth();
        
        month += add;
        if(month>11) {
            month -= 12;
            year++;
        }else if(month<0) {
            month += 12;
            year--;
        }
        var d = new Date(year, month, this._date.getDate());
        return new MyDate(d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate());
    };
}